package utils

import (
	"context"
	"encoding/json"
	"fmt"
	"net/http"
	"os"
	"strconv"
	"strings"
	"time"

	models "yuanaiproblem/dao/model"
	"yuanaiproblem/migrations/redis"
	response "yuanaiproblem/models"
	"yuanaiproblem/models/dto"

	"github.com/gin-gonic/gin"
)

// BuildPromptFromDTO 根据生成题目请求DTO构建AI提示词
// 功能：将用户输入的参数（主题、题型、难度等）转换为AI可理解的自然语言指令
// 参数：包含题目生成参数的请求结构体
// 返回：构建完成的AI提示词字符串
func BuildPromptFromDTO(req dto.GenerateQuestionRequest) string {
	// 基础提示词模板：包含主题、题型、难度、数量等核心参数
	basePrompt := fmt.Sprintf(
		"请生成关于「%s」的%s题, 难度为%s, 共%d道。",
		req.Subject,                   // 题目主题（如"计算机网络"）
		mapQuestionType(req.Type),     // 题型（转换为中文描述，如"单选题"）
		mapDifficulty(req.Difficulty), // 难度（转换为中文描述，如"中等"）
		req.Count,                     // 题目数量
	)

	// 补充用户额外要求（如特殊格式、知识点侧重等）
	if req.ExtraPrompt != "" {
		basePrompt += fmt.Sprintf(" 额外要求：%s", req.ExtraPrompt)
	}

	// 强制AI返回指定JSON格式，确保后续能正确解析
	basePrompt += ` 请严格按照以下JSON格式返回，字段包括：id（题目ID）、content（题目内容）、options（选项，数组）、answer（答案）、analysis（解析）。示例：[{"id":1,"content":"...","options":["..."],"answer":"...","analysis":"..."}]`

	return basePrompt
}

// mapQuestionType 将题型英文标识转换为中文描述
// 参数：题型英文
// 返回：对应的中文描述（如"单选题"）
func mapQuestionType(t string) string {
	switch t {
	case "single":
		return "单选题"
	case "multiple":
		return "多选题"
	case "judge":
		return "判断题"
	case "essay":
		return "简答题"
	default:
		return t // 未知题型直接返回原标识
	}
}

// mapDifficulty 将难度英文标识转换为中文描述
// 参数：难度英文
// 返回：对应的中文描述（如"中等"）
func mapDifficulty(d string) string {
	switch d {
	case "easy":
		return "简单"
	case "medium":
		return "中等"
	case "hard":
		return "困难"
	default:
		return d // 未知难度直接返回原标识
	}
}

// HandleTempQuestions 处理生成的临时题目：存入Redis并关联用户（防止越权访问）
// 将AI生成的临时题目缓存到Redis，设置过期时间，并绑定生成者用户ID
func HandleTempQuestions(c *gin.Context, userID int32, questions []models.Question) (string, error) {
	// 构建缓存数据结构：包含用户ID（权限校验）、题目列表、创建时间
	cacheData := struct {
		UserID    int32             `json:"user_id"`    // 生成者用户ID，用于后续校验
		Questions []models.Question `json:"questions"`  // AI生成的题目列表
		CreatedAt time.Time         `json:"created_at"` // 缓存创建时间（辅助过期判断）
	}{
		UserID:    userID,
		Questions: questions,
		CreatedAt: time.Now(),
	}
	// 序列化为JSON，便于Redis存储和后续解析
	cacheJSON, err := json.Marshal(cacheData)
	if err != nil {
		return "", fmt.Errorf("题目数据序列化失败: %w", err)
	}
	// 存入Redis，设置30分钟过期时间
	ctx := context.Background()
	// 键名格式：temp_questions:用户ID
	redisKey := fmt.Sprintf("temp_questions:%d", userID)
	err = redis.RedisClient.Set(
		ctx,
		redisKey,
		cacheJSON,
		30*time.Minute, // 30分钟后自动过期
	).Err()
	if err != nil {
		return "", fmt.Errorf("redis缓存失败: %w", err)
	}
	return redisKey, nil // 返回缓存键名，用于后续操作（如保存时查询）
}

// BindAndValidate 绑定并验证请求参数（JSON格式）
// 将HTTP请求的JSON体绑定到目标结构体，并返回绑定错误
func BindAndValidate(c *gin.Context, req interface{}) error {
	// 使用ShouldBindJSON绑定JSON参数：自动解析并映射到结构体字段
	if err := c.ShouldBindJSON(req); err != nil {
		return err // 绑定失败返回错误（如格式错误、字段不匹配等）
	}
	return nil // 绑定成功
}

// ParseParamToUint 解析路径参数为uint类型
// 从URL路径中提取参数，转换为uint（无符号整数）
func ParseParamToUint(c *gin.Context, paramName string) (uint, error) {
	paramStr := c.Param(paramName) // 从路径中获取参数字符串（如"/questions/:id"中的"id"）
	if paramStr == "" {
		return 0, fmt.Errorf("缺少%s", paramName) // 参数缺失错误
	}

	// 转换为uint64，再转为uint（确保范围合法）
	paramInt, err := strconv.ParseUint(paramStr, 10, 64)
	if err != nil {
		return 0, fmt.Errorf("无效的%s", paramName)
	}
	return uint(paramInt), nil
}

// HandleAIError 统一处理AI生成相关的错误响应
// 根据AI错误信息，返回对应的HTTP状态码和错误提示
func HandleAIError(c *gin.Context, err error) {
	// 根据错误信息中的关键词匹配对应的状态码
	switch {
	case strings.Contains(err.Error(), "无效的"): // 无效参数（如API密钥错误、参数格式错误）
		response.Error(c, http.StatusBadRequest, err.Error())
	case strings.Contains(err.Error(), "超时"): // 第三方服务超时（如AI接口响应慢）
		response.Error(c, http.StatusGatewayTimeout, err.Error())
	default: // 其他未知错误（如AI服务内部错误）
		response.Error(c, http.StatusInternalServerError, "生成题目失败: "+err.Error())
	}
}

// HandleQuestionServiceError 统一处理题目服务层的错误响应
// 根据题目相关业务错误，返回对应的HTTP状态码和提示
func HandleQuestionServiceError(c *gin.Context, err error) {
	// 根据错误字符串匹配对应的状态码（与服务层返回的错误信息对应）
	switch err.Error() {
	case "题目不存在", "临时题目不存在或已过期": // 资源未找到
		response.Error(c, http.StatusNotFound, err.Error())
	case "无权限更新此题目", "无权限删除此题目", "无权限操作此临时题目": // 权限不足
		response.Error(c, http.StatusForbidden, err.Error())
	case "题目已被试卷引用，无法删除": // 资源冲突（被引用的题目不能删除）
		response.Error(c, http.StatusConflict, err.Error())
	default: // 其他服务端错误（如数据库操作失败）
		response.Error(c, http.StatusInternalServerError, err.Error())
	}
}

// isContainLanguage 检查主题中是否包含系统支持的编程语言
// 支持的语言从环境变量 SUPPORTED_LANGUAGES 读取（默认：go,python）
// IsContainLanguage 检查主题中是否包含环境变量支持的任意编程语言（子串匹配）
func IsContainLanguage(subject string) (bool, []string) {
	// 读取支持的编程语言（带默认值）
	langStr := os.Getenv("SUPPORTED_LANGUAGES")
	if langStr == "" {
		langStr = "go,python"
	}

	// 解析为切片（去重、去空）
	langSet := make(map[string]bool)
	var supportedLangs []string
	for _, lang := range strings.Split(langStr, ",") {
		lang = strings.TrimSpace(lang)
		if lang != "" && !langSet[lang] {
			langSet[lang] = true
			supportedLangs = append(supportedLangs, lang)
		}
	}

	// 统一转为小写，实现不区分大小写的子串匹配
	subjectLower := strings.ToLower(subject)
	for _, lang := range supportedLangs {
		// 检查当前语言是否作为子串包含在主题中
		if strings.Contains(subjectLower, strings.ToLower(lang)) {
			return true, supportedLangs
		}
	}

	return false, supportedLangs
}

// 辅助函数：从环境变量读取float64，默认值兜底
func GetEnvFloat(key string, defaultValue float64) float64 {
	val := os.Getenv(key)
	if val == "" {
		return defaultValue
	}
	res, err := strconv.ParseFloat(val, 64)
	if err != nil {
		return defaultValue
	}
	return res
}

// isModelSupported 检查模型是否在支持列表中
// 参数model：待检查的模型标识
// 返回值：true-支持，false-不支持
func IsModelSupported(model string) bool {
	modelsStr := os.Getenv("SUPPORTED_AI_MODELS")
	if modelsStr == "" {
		modelsStr = "doubao,tongyi" // 未配置时使用默认列表
	}

	// 遍历支持的模型列表进行匹配
	for _, m := range strings.Split(modelsStr, ",") {
		if strings.TrimSpace(m) == model {
			return true
		}
	}
	return false
}

// 辅助函数：字段值合并（a为空时返回b，否则返回a）
func Coalesce[T comparable](a, b T) T {
	var zero T
	if a == zero {
		return b
	}
	return a
}
